package hichecker.university;

import hichecker.controller.Config;
import hichecker.controller.MainController;
import hichecker.ui.components.icon_factory.IconFactory.IconType;
import hichecker.ui.custom_views.passing.PassingCondition.ConditionType;
import hichecker.ui.custom_views.series.SeriesSettings;
import hichecker.ui.custom_views.series.SeriesView;
import hichecker.ui.general_views.container_panel.content.ContentPanel;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

/**
 * Class representing a part of a requirement a student has to solve. It could be a homework sheet,
 * an oral test, an exam, etc.
 */
@XmlRootElement
@XmlType (propOrder = {"id", "tasks"})
public abstract class Series extends UniversityPart {

	private @XmlAttribute String name;

	private @XmlElement(name = "task") List<Task> tasks;
	private @XmlElement UUID id;

	/**
	 * Creates a series, generates a new UUID and sets it's name to
	 * {@link Config#NEW_VALUE_SERIES_NAME a new default value}
	 */
	public Series() {
		id = UUID.randomUUID();
		name = Config.NEW_VALUE_SERIES_NAME;
	}

	/**
	 * Creates a new series and copies all values out of the referenced series
	 *
	 * @param series
	 */
	public Series(Series series) {
		id = series.getID();
		name = series.getName();

		tasks = new ArrayList<Task>();
		for (Task task : series.getTasks())
			tasks.add(new Task(task));
	}

	@Override
	@XmlTransient
	public UUID getID() {
		return id;
	}

	/**
	 * Sets the unique ID
	 *
	 * @param id
	 */
	public void setID(UUID id) {
		this.id = id;
	}

	@Override
	@XmlTransient
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of the series
	 *
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Returns a list of all tasks related to this series
	 *
	 * @return empty list if there are no tasks
	 */
	@XmlTransient
	public List<Task> getTasks() {
		return tasks = (tasks == null ? new ArrayList<Task>() : tasks);
	}

	/**
	 * Returns the task at a specific index, beginning with 0
	 *
	 * @param taskAt
	 * @return {@code null} if there are no tasks or the index is too large
	 */
	public Task getTaskAt(int taskAt) {
		if (tasks == null || taskAt >= tasks.size())
			return null;
		return tasks.get(taskAt);
	}

	/**
	 * Searches for a task with the given id.
	 *
	 * @param id
	 * @return {@code null} if the task can not be found
	 */
	public Task getTaskByID(UUID id) {
		if (tasks == null)
			tasks = new ArrayList<Task>();

		for (Task task : tasks)
			if (task.getID().equals(id)) return task;

		// No such task
		return null;
	}

	/**
	 * Returns the number of tasks related to this series.
	 *
	 * @return
	 */
	public int getNumTasks() {
		if (tasks == null) tasks = new ArrayList<Task>();
		return tasks.size();
	}

	/**
	 * Returns the sum of the maximum points of all tasks related to this series. Tasks that
	 * {@link Task#isOptional() are optional} aren't taken into account.
	 *
	 * @return
	 */
	public float getSumMax() {
		float max = 0;

		if (tasks == null) {
			tasks = new ArrayList<Task>();
		}

		for (Task task : tasks)
			max += task.isOptional() ? 0 : task.getMaxPoints();

		return max;
	}

	/**
	 * Returns the gained points of a student to a specific task
	 *
	 * @param student
	 * @param taskAt index of the task beginning with {@code 0}
	 * @return {@code -1} if the index is too big or {@link Task#getGainedPoints(Student)} returns
	 *         it
	 */
	public float getGainedPoints(Student student, int taskAt) {
		if (tasks == null) tasks = new ArrayList<Task>();
		if (taskAt >= tasks.size()) return -1;

		return tasks.get(taskAt).getGainedPoints(student);
	}

	/**
	 * Sets the gainedPoints of a student to a specific task
	 *
	 * @param student
	 * @param value
	 * @param taskAt
	 *            index of the task beginning with {@code 0}
	 */
	public void setGainedPoints(Student student, float value, int taskAt) {
		if (tasks == null) tasks = new ArrayList<Task>();
		if (taskAt >= tasks.size()) return;

		tasks.get(taskAt).setGainedPoints(student, value);
	}

	/**
	 * Returns the sum of gained points to all tasks of a student
	 *
	 * @param student
	 * @return
	 */
	public float getSumGainedPoints(Student student) {
		float sum = 0;

		boolean noPoints = true;
		for (Task task : getTasks()) {
			float curGain = task.getGainedPoints(student);
			noPoints &= curGain < 0;
			sum += curGain < 0 ? 0 : curGain;
		}

		return noPoints ? -1 : sum;
	}

	/**
	 * Sets the tasks of this series
	 *
	 * @param tasks
	 */
	public void setTasks(List<Task> tasks) {
		this.tasks = tasks;
	}

	@Override
	public List<ConditionType> getPossibleConditions() {
		List<ConditionType> list = new ArrayList<ConditionType>();
		list.add(ConditionType.SUM_POINTS_GREATER_EQUAL);
		return list;
	}

	@Override
	public List<UniversityPart> getSubParts() {
		return null;
	}

	@Override
	public UniversityPart[] getPossibleSubPart() {
		return new UniversityPart[] { new Task() };
	}

	@Override
	public UUID addSubPart(int type) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void removeSubPart(UUID id) {
		// TODO Auto-generated method stub
	}

	@Override
	public ContentPanel getRepresentation(boolean showConfig, MainController controller) {
		return showConfig ? new SeriesSettings(this, controller) : new SeriesView(this, controller);
	}

	@Override
	public IconType getIcon() {
		return IconType.PAGE;
	}

	@Override
	public IconType getAddIcon() {
		return IconType.ADD_SERIES;
	}

	/**
	 * Two series are equal if they have the same {@link #id}
	 */
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Series))
			return false;
		Series that = (Series) o;
		return that.id.equals(this.id);
	}

}
